﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections;

namespace Objects
{
    public class Collection<T> : Valuehost, IList<T>, IChangingObservable, INotifyCollectionChanged
    {
        public override object Value
        {
            get
            {
                return Values;
            }
            set
            {
                this.Inject(value as Valuehost);
            }
        }
        private HashSet<T> hash;
        private List<T> _Values;
        private List<LazyObject<object>> _LazyValues;

        public Collection()
        {
            hash = new HashSet<T>();
            _Values = new List<T>();
            _LazyValues = new List<LazyObject<object>>();
        }

        public override bool Inject(Valuehost value)
        {
            bool res = false;
            ValueInjector vi;
            LazyObject<object> lo;
            T val;
            if (!(res = base.Inject(value)) && (vi = value as ValueInjector) != null)
                if ((lo = vi.Value as LazyObject<object>) != null)
                {
                    Insert(int.MaxValue, default(T), lo);
                    res = true;
                }
                else if (vi.TryCast<T>(out val))
                {
                    Insert(int.MaxValue, val, null);
                    res = true;
                }
            return res;
        }

        protected override void Clone(ref Valuehost chain)
        {
            ICloneable cl;
            Collection<T> res;
            chain = res = (chain as Collection<T>) ?? new Collection<T>();
            res._Values.AddRange(_Values
                .Select(s => (cl = s as ICloneable) != null ? (T)cl.Clone() : s));
            res._LazyValues.AddRange(
                _LazyValues.Select(s => (LazyObject<object>)s.Clone())
            );
        }

        public IEnumerable<T> Values
        {
            get
            {
                for (int i = 0; i < _Values.Count; i++)
                    yield return GetValueAt(i);
            }
        }

        private T GetValueAt(int index)
        {
            T res, old;
            if (_LazyValues[index] != null)
            {
                if (!_Values[index].IsEquals(default(T)))
                    hash.Remove(_Values[index]);
                if (ValueInjector.TryCast<T>(_LazyValues[index].GetObject(), out res))
                {
                    old = _Values[index];
                    _Values[index] = res;
                    if (!res.IsEquals(default(T)))
                        hash.Add(res);
                    OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, res, old, index));
                }
                else
                    _Values[index] = default(T);
                if (_LazyValues[index].IsPermanent)
                    _LazyValues[index] = null;
            }
            return _Values[index];
        }

        #region IList<T> Members

        public int IndexOf(T item)
        {
            int res = -1;
            if (hash.Contains(item))
                res = _Values.IndexOf(item);
            if (res < 0)
                for (int i = 0; i < _LazyValues.Count; i++)
                    if (_LazyValues[i] != null)
                        if (item.IsEquals(GetValueAt(i)))
                        {
                            res = i;
                            break;
                        }
            return res;
        }

        public void Insert(int index, T item, LazyObject<object> lazy)
        {
            if (!item.IsEquals(default(T)))
                hash.Add(item);
            if (index < 0)
                index = 0;
            else if (index > _Values.Count)
                index = _Values.Count;
            _Values.Insert(index, item);
            _LazyValues.Insert(index, lazy);
            OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        }

        public void Insert(int index, T item)
        {
            Insert(index, item, null);
        }

        public void RemoveAt(int index)
        {
            T old;
            if (index >= 0 && index < _Values.Count)
            {
                old = _Values[index];
                if (!old.IsEquals(default(T)))
                    hash.Remove(old);
                _Values.RemoveAt(index);
                _LazyValues.RemoveAt(index);
                OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
            }
        }

        public T this[int index]
        {
            get
            {
                T res = default(T);
                if (index >= 0 && index < _Values.Count)
                    res = GetValueAt(index);
                return res;
            }
            set
            {
                T old = default(T);
                if (index < 0)
                    index = 0;
                if (index < _Values.Count)
                {
                    if (!_Values[index].IsEquals(default(T)))
                        old = _Values[index];
                    _Values[index] = value;
                    OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
                }
                else
                    Insert(int.MaxValue, value, null);
            }
        }

        #endregion

        #region ICollection<T> Members

        public void AddRange(IEnumerable<T> coll)
        {
            if (coll != null)
            {
                foreach (var item in coll)
                {
                    _Values.Add(item);
                    hash.Add(item);
                    _LazyValues.Add(null);
                }
                OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)coll));
            }
        }

        public void AddRange(IEnumerable<T> coll, int index)
        {
            if (coll != null)
            {
                foreach (var item in coll)
                {
                    _Values.Insert(index, item);
                    hash.Add(item);
                    _LazyValues.Insert(index++, null);
                }
                OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, coll, index));
            }
        }

        public void Add(T item)
        {
            Insert(int.MaxValue, item, null);
        }

        public void Clear()
        {
            var v = _Values;
            hash = new HashSet<T>();
            _Values = new List<T>();
            _LazyValues = new List<LazyObject<object>>();
            OnNotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, v));
        }

        public bool Contains(T item)
        {
            return IndexOf(item) >= 0;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            for (int i = 0; i < Count; i++)
                array[arrayIndex + i] = GetValueAt(i);
        }

        public int Count
        {
            get { return _Values.Count; }
        }

        private bool _IsReadOnly = false;
        public bool IsReadOnly
        {
            get { return _IsReadOnly; }
        }

        public bool Remove(T item)
        {
            int ind = IndexOf(item);
            if (ind >= 0)
                RemoveAt(ind);
            return ind >= 0;
        }

        #endregion

        private class ValueEnumerator : IEnumerator<T>
        {
            int pos;
            Collection<T> coll;
            public ValueEnumerator(Collection<T> coll)
            {
                this.pos = -1;
                this.coll = coll;
            }

            #region IEnumerator<T> Members

            public T Current
            {
                get { return coll.GetValueAt(pos); }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                coll = null;
                pos = -1;
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return coll[pos]; }
            }

            public bool MoveNext()
            {
                return ++pos >= coll.Count;
            }

            public void Reset()
            {
                pos = -1;
            }

            #endregion
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return new ValueEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new ValueEnumerator(this);
        }

        #endregion

        private class CollectionChangingObserver : ChangingObserver
        {
            private class State: ChangingObserver.State
            {
                public NotifyCollectionChangedAction Action;
                public int oldStartIndex;
                public int newStartIndex;
                public IList oldItems;
                public IList newItems;
            }

            
            public override object Object
            {
                get
                {
                    return coll;
                }
                protected set
                {
                    base.Object = value;
                    coll = value as Collection<T>;
                    coll.CollectionChanged += coll_CollectionChanged;
                }
            }

            private Collection<T> coll;

            void coll_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (Listening)                
                    Shot(new State()
                    {
                        Action = e.Action,
                        oldStartIndex = (e.OldStartingIndex >= 0) ? e.OldStartingIndex : 0,
                        oldItems = e.OldItems,
                        newStartIndex = e.NewStartingIndex,
                        newItems = e.NewItems
                    });
            }

            #region IChangingObserver Members

            protected override bool Stateup(ChangingObserver.State state)
            {
                if (!base.Stateup(state))
                {
                    State s = state as State;
                    switch (s.Action)
                    {
                        case NotifyCollectionChangedAction.Add:
                            foreach (T item in s.newItems)
                                coll.Remove(item);
                            break;
                        case NotifyCollectionChangedAction.Move:
                            break;
                        case NotifyCollectionChangedAction.Remove:
                            coll.AddRange(s.oldItems.OfType<T>(), s.oldStartIndex);
                            break;
                        case NotifyCollectionChangedAction.Replace:
                            for (int i = 0; i < s.oldItems.Count; i++)
                                coll[i + s.oldStartIndex] = (T)s.oldItems[i];
                            break;
                        case NotifyCollectionChangedAction.Reset:
                            coll.AddRange(s.oldItems.OfType<T>());
                            break;
                        default:
                            break;
                    }
                    return false;
                }
                else
                    return true;
            }

            public override IChangingObserver GetFromCurrent()
            {
                return new CollectionChangingObserver(coll);
            }

            public override void ForceNotifyChanging(string propName)
            {
                
            }

            #endregion

            #region INotifyPropertyChanging Members

            protected void OnPropertyChanging(string PropName)
            {
                if (PropertyChanging != null)
                    PropertyChanging(this, new PropertyChangingEventArgs(PropName));
            }

            public event PropertyChangingEventHandler PropertyChanging;

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                PropertyChanging = null;
            }

            #endregion

            public CollectionChangingObserver(Collection<T> coll)
                :base(coll)
            {
            }
        }

        #region IChangingObservable Members

        public IChangingObserver GetObserver()
        {
            return new CollectionChangingObserver(this);
        }

        #endregion

        #region INotifyCollectionChanged Members

        protected void OnNotifyCollectionChanged(NotifyCollectionChangedEventArgs args)
        {
            if (CollectionChanged != null)
                CollectionChanged(this, args);
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region INotifyPropertyChanging Members

        private void OnPropertyChanging(string propName)
        {
            if (PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(propName));
        }

        public event PropertyChangingEventHandler PropertyChanging;

        #endregion
    }
}
